<?php

namespace mongrove;

/**
 *
 * The StructureField is a proxy for a Structure in a field contextual setting.
 *
 * In the process of defining a Structure, a StructureField can be used to define
 * underlying Structures.
 *
 * @author Gido Hakvoort <gido@hakvoort.it>
 * @author Michiel Hakvoort <michiel@hakvoort.it>
 *
 */
class StructureField extends AbstractField implements CompositeField {

    /**
     * @var Structure
     */
    protected $structure;
    protected $type;

    /**
     * Define a new StructureField over the given Structure.
     *
     * @param Structure $structure The Structural type contained in this StructureField
     */
    public function __construct(Structure $structure = null) {
        parent :: __construct();

        if($structure !== null) {
            $this->type = strtolower(get_class($structure));
            $this->structure = $structure;
        } else {
            $this->structure = new Structure();
        }
    }

    /**
     * Add a new Field to the underlying structure.
     *
     * @param string $name The name of the field
     * @param Field $field The Field that is to be added
     *
     * @return Structure
     */
    public function addField($name, Field $field) {
        $this->structure->addField($name, $field);
        return $this;
    }

    /**
     * Check whether a field of the given name has been defined in the underlying Structure.
     *
     * @param string $name The name for which to check whether a Field with the given name has been defined
     *
     * @return boolean True if a field with the given name has been defined in the underlying Structure
     */
    public function hasField($name) {
        return $this->structure->hasField($name);
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.CompositeField::getField()
     */
    public function getField($name) {
        return $this->structure->getField($name);
    }

    /**
     * Return all fields contained in the underlying Structure.
     *
     * @return array The array of Fields contained by the underlying Structure
     */
    public function getFields() {
        return $this->structure->fields;
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.Field::getValue()
     */
    public function getValue() {
        return $this->structure;
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.AbstractField::setValueImpl()
     */
    protected function setValueImpl($value) {
        if($value instanceof Structure) {

            // check Structure's type
            $observedType = mb_strtolower(get_class($value));
            if($observedType !== $this->type && !is_subclass_of($value, $this->type)) {
                throw new \Exception("Unexpected type: got '{$type}', expected '{$this->type}'");
            }

            // get key value pairs from structure
            $data = array();
            foreach($value->getFields() as $key => $field) {
                if(isset($this->structure[$key])) {
                    $data[$key] = $field->getValue();
                }
            }

            $value = $data;
        }

        // check if $value is an array
        if(!is_array($value)) {
            return;
        }

        // why not just reuse the entire array? -> perhaps array does not match?
        foreach($value as $key => $field) {
            if(isset($this->structure[$key])) {
                $this->structure[$key]->setValue($field);
            }
        }

        // update state
        $this->_state |= self :: STATE_NEW;
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.Field::dehydrate()
     */
    public function dehydrate() {
        return $this->structure->dehydrate();
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.Field::hydrate()
     */
    public function hydrate($value) {
        $this->structure->hydrate($value);
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.Field::getMutations()
     */
    public function getMutations($path = null, $name = null) {
        if($this->isNew()) {
            $path === null ?: $path .= '.';
            return array(array(Command :: OP_SET => array("{$path}{$name}" => $this->dehydrate())));
        } else {
            return $this->structure->getMutations($path, $name);
        }
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.AbstractField::clean()
     */
    public function clean() {
        parent :: clean();
        $this->structure->clean();
    }

    /**
     * (non-PHPdoc)
     * @see src/mongrove.Field::rewriteQuery()
     */
    public function rewriteQuery(array $partialQuery) {
        foreach($partialQuery as $operator => $value) {

            // TODO : add type check
            if($value instanceof Structure) {
                $partialQuery[$operator] = $value->dehydrate();
            }
        }
        return $partialQuery;
    }

    /**
     *
     */
    public function __clone() {
        $this->structure = clone $this->structure;
    }
}